{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [ECMAScript 6 入门-阮一峰](https://es6.ruanyifeng.com/#docs/let)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## let 和 const 命令\n",
    "使用var关键字声明的全局作用域变量属于window对象。\n",
    "\n",
    "使用let关键字声明的全局作用域变量不属于window对象。\n",
    "\n",
    "使用var关键字声明的变量在任何地方都可以修改。\n",
    "\n",
    "在相同的作用域或块级作用域中，不能使用let关键字来重置var关键字声明的变量。\n",
    "\n",
    "在相同的作用域或块级作用域中，不能使用let关键字来重置let关键字声明的变量。\n",
    "\n",
    "let关键字在不同作用域，或不用块级作用域中是可以重新声明赋值的。\n",
    "\n",
    "在相同的作用域或块级作用域中，不能使用const关键字来重置var和let关键字声明的变量。\n",
    "\n",
    "在相同的作用域或块级作用域中，不能使用const关键字来重置const关键字声明的变量\n",
    "\n",
    "const 关键字在不同作用域，或不同块级作用域中是可以重新声明赋值的:\n",
    "\n",
    "var关键字定义的变量可以先使用后声明。\n",
    "\n",
    "let关键字定义的变量需要先声明再使用。\n",
    "\n",
    "const关键字定义的常量，声明时必须进行初始化，且初始化后不可再修改。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### let 命令"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10\n"
     ]
    }
   ],
   "source": [
    "var a = [];\n",
    "for (var i = 0; i < 10; i++) {\n",
    "  a[i] = function () {\n",
    "    console.log(i);\n",
    "  };\n",
    "}\n",
    "a[6](); // 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面代码中，变量`i`是`var`命令声明的，在全局范围内都有效，所以全局只有一个变量`i`。每一次循环，变量i的值都会发生改变，而循环内被赋给数组`a`的函数内部的`console.log(i)`，里面的i指向的就是全局的`i`。也就是说，所有数组`a`的成员里面的`i`，指向的都是同一个`i`，导致运行时输出的是最后一轮的`i`的值，也就是 `10`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n"
     ]
    }
   ],
   "source": [
    "var a = [];\n",
    "for (let i = 0; i < 10; i++) {\n",
    "  a[i] = function () {\n",
    "    console.log(i);\n",
    "  };\n",
    "}\n",
    "a[6](); // 6"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面代码中，变量`i`是`let`声明的，当前的i只在本轮循环有效，所以每一次循环的i其实都是一个新的变量，所以最后输出的是`6`。你可能会问，如果每一轮循环的变量i都是重新声明的，那它怎么知道上一轮循环的值，从而计算出本轮循环的值？这是因为 JavaScript 引擎内部会记住上一轮循环的值，初始化本轮的变量`i`时，就在上一轮循环的基础上进行计算。\n",
    "\n",
    "另外，`for`循环还有一个特别之处，就是设置循环变量的那部分是一个父作用域，而循环体内部是一个单独的子作用域。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 不存在变量提升\n",
    "`var`命令会发生“变量提升”现象，即变量可以在声明之前使用，值为`undefined`。这种现象多多少少是有些奇怪的，按照一般的逻辑，变量应该在声明语句之后才可以使用。\n",
    "\n",
    "为了纠正这种现象，`let`命令改变了语法行为，它所声明的变量一定要在声明后使用，否则报错。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "undefined\n"
     ]
    }
   ],
   "source": [
    "// var 的情况\n",
    "console.log(name);\n",
    "var name = 'anlzou';"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "// let 的情况\n",
    "// console.log(y);\n",
    "let y = 2;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 暂时性死区\n",
    "ES6 明确规定，如果区块中存在let和const命令，这个区块对这些命令声明的变量，从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量，就会报错。\n",
    "\n",
    "总之，在代码块内，使用let命令声明变量之前，该变量都是不可用的。这在语法上，称为“暂时性死区”（temporal dead zone，简称 TDZ）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "undefined\n",
      "123\n"
     ]
    }
   ],
   "source": [
    "if (true) {\n",
    "  // TDZ开始\n",
    "//   tmp = 'abc'; // ReferenceError\n",
    "//   console.log(tmp); // ReferenceError\n",
    "\n",
    "  let tmp; // TDZ结束\n",
    "  console.log(tmp); // undefined\n",
    "\n",
    "  tmp = 123;\n",
    "  console.log(tmp); // 123\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "“暂时性死区”也意味着typeof不再是一个百分之百安全的操作。\n",
    "\n",
    "作为比较，如果一个变量根本没有被声明，使用typeof反而不会报错。\n",
    "\n",
    "所以，在没有let之前，typeof运算符是百分之百安全的，永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯，变量一定要在声明之后使用，否则就报错。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "// typeof x; // ReferenceError\n",
    "let x;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'undefined'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "typeof undeclared_variable // \"undefined\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ES6 规定暂时性死区和`let`、`const`语句不出现变量提升，主要是为了减少运行时错误，防止在变量声明前就使用这个变量，从而导致意料之外的行为。\n",
    "\n",
    "总之，暂时性死区的本质就是，只要一进入当前作用域，所要使用的变量就已经存在了，但是不可获取，只有等到声明变量的那一行代码出现，才可以获取和使用该变量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Javascript (Node.js)",
   "language": "javascript",
   "name": "javascript"
  },
  "language_info": {
   "file_extension": ".js",
   "mimetype": "application/javascript",
   "name": "javascript",
   "version": "13.13.0"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
